﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using AgentSystem.AgentCreator.Sbpel.Activities;
using AgentSystem.AgentCreator.Util;
using System.CodeDom;

namespace AgentSystem.AgentCreator.Sbpel
{
    public class SbpelReader
    {
        /// <value>
        /// The agent file path.
        /// </value>
        public string AgentFilePath { get; private set; }
        
        /// <summary>
        /// Initializes a new instance of the <see cref="SbpelReader"/> class.
        /// </summary>
        /// <param name="agentFilePath">The agent file path.</param>
        public SbpelReader(string agentFilePath)
        {
            AgentFilePath = agentFilePath;
        }

        /// <summary>
        /// Gets the code string.
        /// </summary>
        /// <returns>Code Object Collection</returns>
        public IEnumerable<CodeObject> GetCodeDom()
        {
            AbstractActivity rootActivity = GetActivityTree();
            List<CodeObject> codeObjects = new List<CodeObject>();
            codeObjects.AddRange(rootActivity.GetCodeDom());

            //Add a Main method
            codeObjects.Add(CreateEntryPointClass(rootActivity));

            return codeObjects;
        }

        /// <summary>
        /// Gets the activity tree.
        /// </summary>
        /// <returns>Root Activity</returns>
        public AbstractActivity GetActivityTree()
        {
            //Check if file exists
            if (!File.Exists(AgentFilePath))
            {
                throw new SbperException("File \"{0}\" was not found.", AgentFilePath);
            }

            //Initialize the current and root activities.
            AbstractActivity currentActivity = null;
            ProcessActivity processActivity = null;

            using (XmlTextReader reader = new XmlTextReader(AgentFilePath))
            {
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.XmlDeclaration:
                        case XmlNodeType.ProcessingInstruction:
                        case XmlNodeType.Comment:
                            break;
                        case XmlNodeType.Element:
                        case XmlNodeType.EndElement:
                            ProcessElementNode(reader, ref currentActivity, ref processActivity);
                            break;
                    }
                }
            }

            return processActivity;
        }

        #region Provate Methods

        /// <summary>
        /// Create the Entry point class.
        /// </summary>
        /// <param name="rootActivity"></param>
        /// <returns></returns>
        private static CodeTypeDeclaration CreateEntryPointClass(AbstractActivity rootActivity)
        {
            string agentClassName = (rootActivity as ProcessActivity).GetAgentClassName();
            //Create class that will contain the main entry point
            CodeTypeDeclaration mainEntryPoint = new CodeTypeDeclaration("MainEntryPoint");
            mainEntryPoint.Comments.Add(new CodeCommentStatement("Class that cconains the Main function."));
            CodeMemberMethod mainMethod = new CodeMemberMethod();
            mainMethod.Name = "Main";
            mainMethod.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            mainMethod.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference("System.String[]"), "args"));
            mainMethod.Comments.Add(new CodeCommentStatement("Main method that creates and launches the Agent"));
            mainMethod.Statements.Add(new CodeVariableDeclarationStatement(agentClassName, "agent", new CodeObjectCreateExpression(agentClassName)));
            mainMethod.Statements.Add(new CodeVariableDeclarationStatement("AgentContainer", "agentContainer", new CodeObjectCreateExpression("AgentContainer",new CodeVariableReferenceExpression("agent"), new CodeVariableReferenceExpression("args[0]"))));
            mainMethod.Statements.Add(new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("agentContainer"), "Run"));
            mainEntryPoint.Members.Add(mainMethod);
            return mainEntryPoint;
        }

        #endregion

        #region Private Classes - sBPEL XML Parser

        /// <summary>
        /// Processes the element node.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="currentActivity">The current activity.</param>
        /// <param name="processActivity">The root activity.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void ProcessElementNode(XmlTextReader reader, ref AbstractActivity currentActivity, ref ProcessActivity processActivity)
        {
            if (reader.NodeType == XmlNodeType.Element)
            {
                switch (reader.Name)
                {
                    case Constants.NodeNameProcess:
                        CreateRootActivity(reader, ref currentActivity, ref processActivity);
                        break;
                    case Constants.NodeNameVariables:
                        //Read all variables
                        var variablesCollection = GetVariablesActivities(ref reader, ref currentActivity);
                        processActivity.ChildActivities.Add(variablesCollection);
                        break;
                    case Constants.NodeNameWait:
                        //Add a wait statement
                        CreateWaitActivity(reader, currentActivity);
                        break;
                    case Constants.NodeNameSequence:
                        ProcessSequenceActivity(ref reader, ref currentActivity);
                        break;
                    case Constants.NodeNameAssign:
                        CreateAssignmentActivity(reader, currentActivity);
                        break;

                }
            }
            else
            {
                switch(reader.Name)
                {
                    case Constants.NodeNameSequence:
                        currentActivity = currentActivity.ParentActivity;
                        break;
                }
            }
        }

        /// <summary>
        /// Create Assignment Activity.
        /// </summary>
        /// <param name="reader">XML Reader</param>
        /// <param name="currentActivity">Current Activity</param>
        private static void CreateAssignmentActivity(XmlTextReader reader, AbstractActivity currentActivity)
        {
            using (var subtreeReader = reader.ReadSubtree())
            {
                while (subtreeReader.Read())
                {
                    if (subtreeReader.NodeType == XmlNodeType.Element && subtreeReader.Name == Constants.NodeNameCopy)
                    {
                        var fromNode = subtreeReader.ReadToDescendant(Constants.NodeNameFrom);
                        var fromVariable = subtreeReader.GetAttribute(Constants.NodeAttributeVariable);
                        var fromPart = subtreeReader.GetAttribute(Constants.NodeAttributePart);
                        var fromValue = subtreeReader.GetAttribute(Constants.NodeAttributeValue);
                        var fromValueType = subtreeReader.GetAttribute(Constants.NodeAttributeType);

                        var toNode = subtreeReader.ReadToNextSibling(Constants.NodeNameTo);
                        var toVariable = subtreeReader.GetAttribute(Constants.NodeAttributeVariable);
                        var toPart = subtreeReader.GetAttribute(Constants.NodeAttributePart);

                        if ((!string.IsNullOrEmpty(fromVariable) || !string.IsNullOrEmpty(fromValue)) && !string.IsNullOrEmpty(toVariable))
                        {
                            currentActivity.ChildActivities.Add(new AssignAcivity(currentActivity, fromVariable, fromPart, toVariable, toPart, fromValue, fromValueType));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Processes the sequence activity.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="currentActivity">The current activity.</param>
        /// <returns></returns>
        private static void ProcessSequenceActivity(ref XmlTextReader reader, ref AbstractActivity currentActivity)
        {
                string name = reader.GetAttribute(Constants.NodeAttributeName);
                SequenceActivity sequence = new SequenceActivity(currentActivity, name);
                currentActivity.ChildActivities.Add(sequence);
                currentActivity = sequence;
        }

        /// <summary>
        /// Creates the wait activity.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="currentActivity">The current activity.</param>
        private static void CreateWaitActivity(XmlTextReader reader, AbstractActivity currentActivity)
        {
            string waitFor = reader.GetAttribute(Constants.NodeAttributeFor);
            string waitUntil = reader.GetAttribute(Constants.NodeAttributeUntil);
            string name = reader.GetAttribute(Constants.NodeAttributeName);

            currentActivity.ChildActivities.Add(new WaitActivity(currentActivity, name, waitFor, waitUntil));
        }

        /// <summary>
        /// Gets the variables activities.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns></returns>
        private static VariablesActivity GetVariablesActivities(ref XmlTextReader reader, ref AbstractActivity currentActivity)
        {
            VariablesActivity variableCollection = new VariablesActivity(currentActivity);

            //Get variables sub-tree 
            using (var subtreeReader = reader.ReadSubtree())
            {
                while (subtreeReader.Read())
                {
                    if (subtreeReader.NodeType == XmlNodeType.Element && subtreeReader.Name == Constants.NodeNameVariable)
                    {
                        var variableName = subtreeReader.GetAttribute(Constants.NodeAttributeName);
                        var variableType = subtreeReader.GetAttribute(Constants.VariableTypeAttribute);
                        if (!string.IsNullOrEmpty(variableName) && !string.IsNullOrEmpty(variableType))
                        {
                            variableCollection.ChildActivities.Add(new VariableActivity(variableCollection, variableName, variableType));
                        }
                    }
                }
            }

            return variableCollection;
        }

        /// <summary>
        /// Creates the root activity.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="currentActivity">The current activity.</param>
        /// <param name="rootActivity">The root activity.</param>
        /// <exception cref="SbperException">The \{0}\ can have only one \process\ node.</exception>
        private void CreateRootActivity(XmlTextReader reader, ref AbstractActivity currentActivity, ref ProcessActivity rootActivity)
        {
            //Check if we already have a process node.
            if (rootActivity != null)
            {
                throw new SbperException("The \"{0}\" can have only one \"process\" node.", AgentFilePath);
            }

            //Get process name
            var processName = reader.GetAttribute(Constants.NodeAttributeName);

            //Create a process node
            rootActivity = new ProcessActivity(null, processName);
            currentActivity = rootActivity;
        }
        #endregion
    }
}
